/*
 * WorkItems.hpp
 *
 *  Created on: 18 avr. 2012
 *      Author: jd219546
 */

#ifndef WORKITEMS_HPP_
#define WORKITEMS_HPP_

#include <vector>
using namespace std;

namespace WorkItems
{
struct Solution
{
	const string ref_seq_id;
	const string other_seq_id;
	int ref_start;
	int ref_end;
	int other_start;
	int other_end;
	Solution(const string & ref_seq, const string & other_ref,
			int ref_start, int ref_end, int other_start, int other_end):
		ref_seq_id(ref_seq),other_seq_id(other_ref),
		ref_start(ref_start),ref_end(ref_end),other_start(other_start),other_end(other_end){}
	void print() const
	{
		cout << ref_start << " " << ref_end << " " << other_start << " " << other_end << endl;
	}
};
	class WorkItem
	{
	public:
		virtual ~WorkItem(){}
		virtual	void execute(tbb::parallel_do_feeder<WorkItem*>& feeder, const Sequence & ref_seq,
				const int & minimum_sequence_length = 0) = 0;
	};



	class CompareSeqItem : public WorkItem
	{
		const Sequence & ref_seq;
		vector<Sequence> sequences_to_compare;
		vector<Solution *> my_solutions;
		int amount_of_work_to_do;

		vector<int> comparisons;

		void body_comparison(int*& row_ptr, size_t i, const size_t& columns,
			size_t j, const string& refSeq, const string& otherSeq)
		{
			// if the characters are the same,
			// increase the consecutive matching score from the previous cell
			int& cur_elt = row_ptr[i * columns + j];
			if (refSeq[i] == otherSeq[j])
			{
				cur_elt = row_ptr[(i - 1) * columns + j - 1] + 1;
			} else
				// or reset the matching score to 0
				cur_elt = 0;
		}
		void body_comparison_first_row(int*& row_ptr, const size_t& columns,
			size_t j, const string& refSeq, const string& otherSeq)
		{
			// if the characters are the same,
			// increase the consecutive matching score from the previous cell
			int& cur_elt = row_ptr[j];
			if (refSeq[0] == otherSeq[j])
				cur_elt = 1;
			else
				// or reset the matching score to 0
				cur_elt = 0;
		}
		void body_comparison_first_col(int*& row_ptr, const size_t& columns,
			size_t j, const string& refSeq, const string& otherSeq)
		{
			// if the characters are the same,
			// increase the consecutive matching score from the previous cell
			int& cur_elt = row_ptr[j*columns];
			if (refSeq[j] == otherSeq[0])
				cur_elt = 1;
			else
				// or reset the matching score to 0
				cur_elt = 0;
		}
		void compare_ref_sequence_with_other(const Sequence& seq,int minimum_sequence_length)
		{
			cout << this << " comparing 1 sequence with minimum " << minimum_sequence_length << endl;
			const string& refSeq = ref_seq.my_sequence;
			const string& otherSeq = seq.my_sequence;
			const size_t rows = refSeq.length();
			const size_t columns = otherSeq.length();
			const size_t matrix_size = rows*columns;
			comparisons.resize(matrix_size);
			int* row_ptr = &(comparisons.front());
			const char * other_ptr = otherSeq.c_str();
			const char * ref_ptr = refSeq.c_str();

			//TReating separatedly first row
#if CILK_AUTHORIZED
			row_ptr[0:columns] = (ref_ptr[0] == other_ptr[0:columns]);
#else
			for (size_t j = 0; j < columns; j++)
				body_comparison_first_row(row_ptr, columns, j, refSeq, otherSeq);
#endif
			//Treating separatedly first column
#if CILK_AUTHORIZED
			row_ptr[0:rows:columns] = (ref_ptr[0:rows] == other_ptr[0]);
#else
			for (size_t i = 0; i < rows; i++)
				body_comparison_first_col(row_ptr, columns, i, refSeq, otherSeq);
#endif
			const int * prev_row_ptr = row_ptr;
			row_ptr += columns + 1;
			const char * shifted_other_ptr = other_ptr + 1;
			const int length_to_compare = columns-1;
			for (size_t i = 1; i < rows; i++) {
				// iteration over the characters of the sequence to compare
#if CILK_AUTHORIZED
	//			cout << "COMPing : " << i << endl;
				row_ptr[0:length_to_compare] = (ref_ptr[i] == shifted_other_ptr[0:length_to_compare]);//putting 1 or 0
		//		cout << "COMP OK : " << i << endl;
				//cout << "previous: " << prev_beg << " to " << prev_endd << " vs max = " << matrix_size << endl;
				row_ptr[0:length_to_compare] += row_ptr[0:length_to_compare] * prev_row_ptr[0:length_to_compare];//incrementing by 1 or 0 * previous count
			//	cout << "AFTER END" << endl;
				row_ptr += columns;
				prev_row_ptr += columns;
#else
				for (size_t j = 1; j < columns; j++) {
					// if the characters are the same,
					// increase the consecutive matching score from the previous cell
					body_comparison(row_ptr, i, columns, j, refSeq, otherSeq);
				}
#endif
		}
		row_ptr = &(comparisons.front());
		// output the matches for this sequence
		// length must be at least minMatchLength
		// and the longest possible.
		for (size_t i = 0; i < rows; i++) {
			for (size_t j = 0; j < columns; j++) {
				int cur_elt = row_ptr[i * columns + j];
				if (cur_elt >= minimum_sequence_length) {
					//this sequence is part of a longer one
					if (i + 1 < rows && j + 1 < columns
							&& cur_elt <= row_ptr[(i + 1) * columns + j + 1])
						continue;

					//this sequence is part of a longer one
					if (i < rows && j + 1 < columns
							&& cur_elt <= row_ptr[i * columns + j + 1])
						continue;

					//this sequence is part of a longer one
					if (i + 1 < rows && j < columns
							&& cur_elt <= row_ptr[(i + 1) * columns + j])
						continue;
					my_solutions.push_back(
							new Solution(
									ref_seq.my_id, seq.my_id,
							i - cur_elt + 2 , i + 1 , j - cur_elt + 2 , j + 1 )
							);
					//cout << i - cur_elt + 2 << " " << i + 1 << " "
					//		<< j - cur_elt + 2 << " " << j + 1 << "\n";
				}
			}

		}

	}

public:
	static const int MAX_WORK_PER_TASK = 1000000;

		virtual ~CompareSeqItem(){
			for(vector<Solution *>::const_iterator it = my_solutions.begin(); it != my_solutions.end() ; it++)
			delete (*it);
		}
		CompareSeqItem(const Sequence & ref_seq):ref_seq(ref_seq){amount_of_work_to_do = 0;}
		void print_solutions() const
		{
			const string * old_solution_name = 0;
			for(vector<Solution *>::const_iterator it = my_solutions.begin() ;
					it != my_solutions.end() ;it++)
			{
				if (!old_solution_name)
				{
					cout << (*it)->other_seq_id << endl;
					old_solution_name = &(*it)->other_seq_id;
				}
				else
					if (*old_solution_name != (*it)->other_seq_id)
					{
						cout << (*it)->other_seq_id << endl;
						old_solution_name = &(*it)->other_seq_id;
					}
				(*it)->print();
			}
		}
		void execute(tbb::parallel_do_feeder<WorkItem*>& feeder, const Sequence & ref_seq,
				const int & minimum_sequence_length = 0)
		{
			//compare to reference sequence
			//cout << this << " has " << amount_of_work_to_do << " operations" << endl;
			for (vector<Sequence>::const_iterator it = sequences_to_compare.begin();
				it != sequences_to_compare.end() ; it++)
			{
				compare_ref_sequence_with_other(*it, minimum_sequence_length);
			}
		}
		bool has_sequences() const {return get_amount_of_sequences();}
		void add_sequence(const Sequence & seq)
		{
			sequences_to_compare.push_back(seq);
			amount_of_work_to_do+=seq.length()*ref_seq.length();
		}
		void clear_sequences_list(){sequences_to_compare.clear();amount_of_work_to_do = 0;}
		int get_amount_of_sequences() const{return sequences_to_compare.size();}
		int get_amount_of_work_to_do() const{return amount_of_work_to_do;}
	};

	class FileReadItem : public WorkItem
	{

		const char * filename;
		const int max_amount_of_work_to_do;
		const int my_file_id;
		vector< CompareSeqItem *> sequences_to_compare;
		void generate_compare_work_item(CompareSeqItem & tmp,
				tbb::parallel_do_feeder<WorkItem*>& feeder)
		{
			cout << "Adding a sequence_item with amount of work : " << tmp.get_amount_of_work_to_do() << endl;
			CompareSeqItem * new_tmp = new CompareSeqItem(tmp);
			sequences_to_compare.push_back(new_tmp);
			feeder.add(new_tmp);
		}

	public:
		virtual ~FileReadItem(){}
		FileReadItem(const char * filename, int file_id, const int max_amount_of_work_to_do = CompareSeqItem::MAX_WORK_PER_TASK)
		:filename(filename), max_amount_of_work_to_do(max_amount_of_work_to_do), my_file_id(file_id)
		{

		}

		void print_solutions() const
		{
			for (vector< CompareSeqItem *>::const_iterator it = sequences_to_compare.begin()
					; it != sequences_to_compare.end() ; it++)
				(*it)->print_solutions();
		}

		void execute(tbb::parallel_do_feeder<WorkItem*>& feeder, const Sequence & ref_seq,
				const int & minimum_sequence_length = 0)
		{
			//read sequences from file and add them to a new CompareSeqItem or multiples
			CompareSeqItem tmp(ref_seq);
			ifstream seqFile(filename,ios::in);
			while(!seqFile.eof()){
				cout << "Adding a sequence to compare" << endl;
				tmp.add_sequence(seqFile);
				if (tmp.get_amount_of_work_to_do() >= max_amount_of_work_to_do)
				{
					generate_compare_work_item(tmp,feeder);
					tmp.clear_sequences_list();
				}
			}
			if (tmp.has_sequences())
			{
				generate_compare_work_item(tmp,feeder);
			}
		}
	};



};

#endif /* WORKITEMS_HPP_ */
